package net.sf.ivyide;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.JDOMExternalizable;
import com.intellij.openapi.util.WriteExternalException;
import org.apache.ivy.core.IvyContext;
import org.apache.ivy.core.settings.IvySettings;
import org.apache.ivy.core.settings.IvyVariableContainer;
import org.apache.ivy.core.settings.IvyVariableContainerImpl;
import org.apache.ivy.util.DefaultMessageLogger;
import org.apache.ivy.util.filter.ArtifactTypeFilter;
import org.apache.ivy.util.filter.FilterHelper;
import org.apache.ivy.util.Message;
import net.sf.ivyide.ivy.IvyResolvedModule;
import org.apache.ivy.Ivy;
import org.apache.ivy.util.filter.Filter;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.ProjectHelper;
import org.apache.tools.ant.types.FileSet;
import org.jdom.Attribute;
import org.jdom.Element;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.text.ParseException;
import java.util.*;
import java.util.regex.Pattern;

public class MyAntEnvironmentInfo implements ModuleProviderFactory, Cloneable, JDOMExternalizable {

    private static final Logger LOGGER = Logger.getInstance(ModuleProviderFactory.class.getName());

    private File m_ivyInitBuildFile;
    private String m_ivyInitTarget = "quick_init";
    private String ivySettingFileProperty = "ivy.conf.file";
    private String m_ivyBuildListReference = "ide.buildlist.fileset";
    private String m_projectInitTarget = "quick_init";
    private List<ContentRootTemplate> m_rootTemplates;
    private List<ContentRootTemplate> m_globalRootTemplates = new LinkedList<ContentRootTemplate>();
    private String m_artifactFilterString = "jar,sar,zip,rar";
    private String m_ivyConfigurations = "default,test,runtime";
    private String m_ivySourceConfigurations = "source,src";
    private String m_ivyJavadocConfigurations = "docs";
    private static final Pattern DEFAULT_INCLUDED_SUBDIRS = Pattern.compile("metadata|web");
    private String m_compilerOutputPathTemplate;
    private String m_compilerOutputPathForTestsTemplate;

    private void defaultRootTemplates() {
        ContentRootTemplate vobTemplate = new ContentRootTemplate("${basedir}");
        vobTemplate.setMainJavaDirTemplate("${main.java.dir}");
        vobTemplate.setTestJavaDirTemplate("${test.java.dir}");
        vobTemplate.setMainResourceDirTemplate("${main.resource.dir}");
        vobTemplate.setTestResourceDirTemplate("${test.resource.dir}");
        vobTemplate.setIncludedSubdirsPattern(DEFAULT_INCLUDED_SUBDIRS);
        ContentRootTemplate buildTemplate = new ContentRootTemplate("${project.build.dir}");
        buildTemplate.setMainJavaDirTemplate("${gen.main.java.dir}");
        buildTemplate.setTestJavaDirTemplate("${gen.test.java.dir}");
        buildTemplate.setMainResourceDirTemplate("${gen.main.resource.dir},${main.build.dir}/hibernate");
        buildTemplate.setTestResourceDirTemplate("${gen.test.resource.dir}");
        List<ContentRootTemplate> templates = new LinkedList<ContentRootTemplate>();
        templates.add(vobTemplate);
        templates.add(buildTemplate);
        ContentRootTemplate contentRootTemplate = new ContentRootTemplate("${properties.build.dir}");
        contentRootTemplate.setMainResourceDirTemplate("${properties.build.dir}");
        templates.add(contentRootTemplate);
        //ContentRootTemplate buildDirRootTemplate = new ContentRootTemplate("${build.component.dir}");
        //buildDirRootTemplate.setIncludedSubdirsPattern(BUILD_DIR_INCLUDED_SUBDIRS);
        //templates.add(buildDirRootTemplate);
        m_rootTemplates = templates;
        m_compilerOutputPathTemplate = "${main.class.dir}";
        m_compilerOutputPathForTestsTemplate = "${test.class.dir}";
    }

    public String getCompilerOutputPathTemplate() {
        return m_compilerOutputPathTemplate;
    }

    public void setCompilerOutputPathTemplate(String compilerOutputPathTemplate) {
        m_compilerOutputPathTemplate = compilerOutputPathTemplate;
    }

    public String getCompilerOutputPathForTestsTemplate() {
        return m_compilerOutputPathForTestsTemplate;
    }

    public void setCompilerOutputPathForTestsTemplate(String compilerOutputPathForTestsTemplate) {
        m_compilerOutputPathForTestsTemplate = compilerOutputPathForTestsTemplate;
    }

    public void setIvyInitBuildFile(File ivyInitBuildFile) {
        m_ivyInitBuildFile = ivyInitBuildFile;
    }

    public String getIvySettingFileProperty() {
        return ivySettingFileProperty;
    }

    public void setIvySettingFileProperty(String ivySettingFileProperty) {
        this.ivySettingFileProperty = ivySettingFileProperty;
    }

    public void setIvyInitTarget(String ivyInitTarget) {
        m_ivyInitTarget = ivyInitTarget;
    }

    public void setIvyBuildListReference(String ivyBuildListReference) {
        m_ivyBuildListReference = ivyBuildListReference;
    }

    public void setProjectInitTarget(String projectInitTarget) {
        m_projectInitTarget = projectInitTarget;
    }

    public File getIvyInitBuildFile() {
        return m_ivyInitBuildFile;
    }

    public String getIvyInitTarget() {
        return m_ivyInitTarget;
    }

    public String getIvyBuildListReference() {
        return m_ivyBuildListReference;
    }

    public String getProjectInitTarget() {
        return m_projectInitTarget;
    }

    public ModuleProvider createModuleProvider() {
        return new MyIvyEnvironment();
    }

    public Filter getArtifactFilter() {
        if ("".equals(m_artifactFilterString) || m_artifactFilterString == null) {
            return FilterHelper.NO_FILTER;
        } else {
            String[] strings = m_artifactFilterString.split(",");
            return new ArtifactTypeFilter(Arrays.asList(strings));
        }
    }

    public String getArtifactFilterString() {
        return m_artifactFilterString;
    }

    public void setArtifactFilterString(String artifactFilterString) {
        m_artifactFilterString = artifactFilterString;
    }

    public String getIvyConfigurations() {
        return m_ivyConfigurations;
    }

    public void setIvyConfigurations(String ivyConfigurations) {
        m_ivyConfigurations = ivyConfigurations;
    }

    public String getIvySourceConfigurations() {
        return m_ivySourceConfigurations;
    }

    public void setIvySourceConfigurations(String ivySourceConfigurations) {
        m_ivySourceConfigurations = ivySourceConfigurations;
    }

    public String getIvyJavadocConfigurations() {
        return m_ivyJavadocConfigurations;
    }

    public void setIvyJavadocConfigurations(String ivyJavadocConfigurations) {
        m_ivyJavadocConfigurations = ivyJavadocConfigurations;
    }

    public void readExternal(Element element) throws InvalidDataException {
        Attribute niDir = element.getAttribute("niDir");
        if (niDir != null) {
            m_ivyInitBuildFile = new File(niDir.getValue(), "components/build/ant/core.xml");
        } else {
            m_ivyInitBuildFile = new File(element.getAttribute("ivyInitBuildFile").getValue());
            m_ivyBuildListReference = element.getAttribute("ivyBuildListReference").getValue();
            m_ivyInitTarget = element.getAttribute("ivyInitTarget").getValue();
            ivySettingFileProperty = getAttribute(element, "ivySettingsFileProperty", ivySettingFileProperty);
            m_projectInitTarget = element.getAttribute("projectInitTarget").getValue();
            String value = getAttribute(element, "main.compiler.output.path.template");
            if (value != null) {
                m_compilerOutputPathTemplate = value;
            }
            value = getAttribute(element, "test.compiler.output.path.template");
            if (value != null) {
                m_compilerOutputPathForTestsTemplate = value;
            }
            readRootTemplates(m_rootTemplates, element, "contentRootTemplates");
            readRootTemplates(m_globalRootTemplates, element, "globalRootTemplates");
            m_artifactFilterString = getAttribute(element, "artifactFilter", m_artifactFilterString);
            m_ivyConfigurations = getAttribute(element, "ivyConfigurations", m_ivyConfigurations);
            m_ivySourceConfigurations = getAttribute(element, "ivySourceConfigurations", m_ivySourceConfigurations);
            m_ivyJavadocConfigurations = getAttribute(element, "ivyJavadocConfigurations", m_ivyJavadocConfigurations);
        }
    }

    private void readRootTemplates(List<ContentRootTemplate> rootTemplates, Element root, String elementName) {
        rootTemplates.clear();
        Element child = root.getChild(elementName);
        if (child != null) {
            List<Element> children = child.getChildren("contentRootTemplate");
            for (Element contentRootElement : children) {
                Attribute rootAttribute = contentRootElement.getAttribute("root.dir");
                if (rootAttribute == null) {
                    System.out.println("contentRootElement without root: " + contentRootElement);
                } else {
                    String rootDir = rootAttribute.getValue();
                    ContentRootTemplate contentRootTemplate = new ContentRootTemplate(rootDir);
                    contentRootTemplate.setMainJavaDirTemplate(getAttribute(contentRootElement, "main.java.dir"));
                    contentRootTemplate.setTestJavaDirTemplate(getAttribute(contentRootElement, "test.java.dir"));
                    contentRootTemplate.setMainResourceDirTemplate(getAttribute(contentRootElement, "main.resource.dir"));
                    contentRootTemplate.setTestResourceDirTemplate(getAttribute(contentRootElement, "test.resource.dir"));
                    String value = getAttribute(contentRootElement, "include.dir.pattern");
                    if (value != null) {
                        contentRootTemplate.setIncludedSubdirsPattern(Pattern.compile(value));
                    }
                    rootTemplates.add(contentRootTemplate);
                }
            }
        }
    }

    private String getAttribute(Element contentRootElement, String name) {
        Attribute attribute = contentRootElement.getAttribute(name);
        if (attribute != null) {
            return attribute.getValue();
        } else {
            return null;
        }
    }

    private String getAttribute(Element contentRootElement, String name, String defaultValue) {
        String s = getAttribute(contentRootElement, name);
        return s != null ? s : defaultValue;
    }

    private boolean getAttribute(Element contentRootElement, String name, boolean defaultValue) {
        String attribute = getAttribute(contentRootElement, name, Boolean.toString(defaultValue));
        return Boolean.parseBoolean(attribute);
    }

    public void writeExternal(Element element) throws WriteExternalException {
        element.setAttribute("ivyInitBuildFile", m_ivyInitBuildFile.getPath());
        element.setAttribute("ivyBuildListReference", m_ivyBuildListReference);
        element.setAttribute("ivyInitTarget", m_ivyInitTarget);
        element.setAttribute("ivySettingsFileProperty", ivySettingFileProperty);
        element.setAttribute("projectInitTarget", m_projectInitTarget);
        element.setAttribute("ivyConfigurations", m_ivyConfigurations);
        element.setAttribute("artifactFilter", m_artifactFilterString);
        element.setAttribute("ivySourceConfigurations", m_ivySourceConfigurations);
        element.setAttribute("ivyJavadocConfigurations", m_ivyJavadocConfigurations);
        element.setAttribute("main.compiler.output.path.template", m_compilerOutputPathTemplate);
        element.setAttribute("test.compiler.output.path.template", m_compilerOutputPathForTestsTemplate);
        element.setAttribute("version", "1.0");
        Element contentRootElement = new Element("contentRootTemplates");
        element.addContent(contentRootElement);
        writeRootElements(m_rootTemplates, contentRootElement);
        contentRootElement = new Element("globalRootTemplates");
        element.addContent(contentRootElement);
        writeRootElements(m_globalRootTemplates, contentRootElement);

    }

    private void writeRootElements(List<ContentRootTemplate> rootTemplates, Element parent) {
        for (ContentRootTemplate contentRootTemplate : rootTemplates) {
            Element contentRootElement = new Element("contentRootTemplate");
            parent.addContent(contentRootElement);
            setAttribute(contentRootElement, "root.dir", contentRootTemplate.getRootDirTemplate());
            setAttribute(contentRootElement, "main.java.dir", contentRootTemplate.getMainJavaDirTemplate());
            setAttribute(contentRootElement, "test.java.dir", contentRootTemplate.getTestJavaDirTemplate());
            setAttribute(contentRootElement, "main.resource.dir", contentRootTemplate.getMainResourceDirTemplate());
            setAttribute(contentRootElement, "test.resource.dir", contentRootTemplate.getTestResourceDirTemplate());
            setAttribute(contentRootElement, "include.dir.pattern", contentRootTemplate.getIncludedSubdirsPattern() != null ? contentRootTemplate.getIncludedSubdirsPattern().pattern() : null);
        }
    }

    private void setAttribute(Element element, String name, String value) {
        if (value != null) {
            element.setAttribute(name, value);
        }
    }

    //Used by XML
    public MyAntEnvironmentInfo() {
        defaultRootTemplates();
    }

    public MyAntEnvironmentInfo clone() {
        try {
            MyAntEnvironmentInfo clone = (MyAntEnvironmentInfo) super.clone();
            clone.m_globalRootTemplates = deepCloneCollection(m_globalRootTemplates);
            clone.m_rootTemplates = deepCloneCollection(m_rootTemplates);
            return clone;
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }

    private static List<ContentRootTemplate> deepCloneCollection(Collection<ContentRootTemplate> original) throws CloneNotSupportedException {
        List<ContentRootTemplate> clone = new LinkedList<ContentRootTemplate>();
        for (ContentRootTemplate template : original) {
            clone.add(template.clone());
        }
        return clone;
    }

    private ModuleId getModuleId(File ivyFile) {
        try {
            IvyModuleSaxHandler handler = new IvyModuleSaxHandler();
            //BufferedReader reader = new BufferedReader(new FileReader(ivyFile));
            FileInputStream reader = new FileInputStream(ivyFile);
            try {
                SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
                saxParserFactory.setNamespaceAware(true);
                SAXParser parser = saxParserFactory.newSAXParser();

                parser.parse(reader, handler);
                //XMLReader parser = org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
                //parser.setContentHandler(handler);
                //parser.setErrorHandler(handler);
                //InputSource inputSource = new InputSource(reader);
                //parser.parse(inputSource);
            } catch (SAXException e) {
                if (IvyModuleSaxHandler.OK.equals(e.getMessage())) {
                    return handler.getModuleId();
                } else {
                    throw e;
                }
            } finally {
                reader.close();
            }
        } catch (Exception e) {
            throw new RuntimeException("Unable to parse ivy file " + ivyFile + ": " + e);
        }
        return null;
    }

    public List<ContentRootTemplate> getProjectRootTemplates() {
        return m_rootTemplates;
    }

    public List<ContentRootTemplate> getGlobalRootTemplates() {
        return m_globalRootTemplates;
    }

    private class MyModuleInfo implements ModuleSourceDescriptor {

        private File m_moduleDir;
        private Project m_project;

        public MyModuleInfo(File moduleDir) {
            m_moduleDir = moduleDir;
        }

        private Project getProject() {
            if (m_project == null) {
                File file = new File(m_moduleDir, "build.xml");
                LOGGER.debug("Running ant target  " + m_projectInitTarget + " on " + file);
                m_project = new Project();
                m_project.init();

                ProjectHelper.configureProject(m_project, file);
                m_project.executeTarget(m_projectInitTarget);
                LOGGER.debug("Ant target  " + m_projectInitTarget + " run on " + file);
            }
            return m_project;
        }

        public ContentRoot[] getContentRoots() {
            List<ContentRoot> contentRoots = new LinkedList<ContentRoot>();
            for (ContentRootTemplate contentRootTemplate : m_rootTemplates) {
                AntContentRoot antContentRoot = new AntContentRoot(contentRootTemplate, getProject());
                if (antContentRoot.getDir() != null) {
                    contentRoots.add(antContentRoot);
                }
            }
            return contentRoots.toArray(new ContentRoot[contentRoots.size()]);
        }

        public File getCompilerOutputPath() {
            return AntContentRoot.getFileFromTemplate(m_compilerOutputPathTemplate, getProject(), false);
        }

        public File getCompilerOutputPathForTests() {
            return AntContentRoot.getFileFromTemplate(m_compilerOutputPathForTestsTemplate, getProject(), false);
        }

    }

    private class IvyModuleSaxHandler extends DefaultHandler {
        private ModuleId m_moduleId;
        private static final String OK = "OK";

        public ModuleId getModuleId() {
            return m_moduleId;
        }

        public void startElement(String string, String string1, String string2, Attributes attributes) throws SAXException {
            if ("info".equals(string1)) {
                m_moduleId = new ModuleId(attributes.getValue("organisation"), attributes.getValue("module"));
                throw new SAXException(OK);
            }
        }
    }

    private class MyIvyEnvironment implements ModuleProvider {

        private Project m_project;
        private Ivy m_ivy;

        private MyIvyEnvironment() {
            if (m_ivyInitBuildFile != null && m_ivyInitBuildFile.exists()) {
                m_project = new Project();
                m_project.init();

                ProjectHelper.configureProject(m_project, m_ivyInitBuildFile);
                m_project.executeTarget(m_ivyInitTarget);
                final Hashtable antProjectProperties = m_project.getProperties();
                IvySettings ivySettings = new IvySettings();
                ivySettings.setVariableContainer(new IvyVariableContainerImpl() {
                    @Override
                    public String getVariable(String name) {
                        Object antPropertyValue = antProjectProperties.get(name);
                        if (antPropertyValue != null) {
                            return (String)antPropertyValue;
                        }
                        return super.getVariable(name);
                    }
                });
                m_ivy = new Ivy();
                m_ivy.setSettings(ivySettings);
                m_ivy.bind();
                Object ivyConfFile = m_project.getProperties().get(ivySettingFileProperty);
                if (ivyConfFile == null) {
                    throw new RuntimeException("Settings file property " + ivySettingFileProperty + " not defined after calling target " + m_ivyInitTarget);
                }
                try {
                    m_ivy.configure(new File(ivyConfFile.toString()));
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
                //Object reference = m_project.getReference("ivy.instance");
                //if (reference == null) {
//                    throw new RuntimeException("Ivy is not correctly initialized after calling target " + m_ivyInitTarget);
  //              }
    //            m_ivy = (Ivy) reference;
                Message.setDefaultLogger(new DefaultMessageLogger(Message.MSG_INFO));
                FileSet buildPaths = (FileSet) m_project.getReference(m_ivyBuildListReference);
                if (buildPaths == null) {
                    throw new RuntimeException("Ivy build list reference '" + m_ivyBuildListReference + "' is not available after calling target " + m_ivyInitTarget);
                }
                getModules();//try to parse all modules
            } else {
                if (m_ivyInitBuildFile == null) {
                    throw new RuntimeException("Please provide a build file");
                } else {
                    throw new RuntimeException("Build file does not exist: " + m_ivyInitBuildFile);
                }
            }
        }

        public ResolvedModule resolveModule(File moduleDir) {
            return new IvyResolvedModule(moduleDir, m_ivy, MyAntEnvironmentInfo.this);
        }

        public ModuleSourceDescriptor getModuleSource(File moduleDir) {
            return new MyModuleInfo(moduleDir);
        }

        public Map<ModuleId, File> getModules() {
            FileSet buildPaths = (FileSet) m_project.getReference(m_ivyBuildListReference);
            DirectoryScanner directoryScanner = buildPaths.getDirectoryScanner(m_project);
            String[] fileNames = directoryScanner.getIncludedFiles();
            Map<ModuleId, File> components = new LinkedHashMap<ModuleId, File>();
            for (String fileName : fileNames) {
                File file = new File(directoryScanner.getBasedir(), fileName);
                File moduleDir = file.getParentFile();
                File ivyFile = new File(moduleDir, "ivy.xml");
                ModuleId moduleId = getModuleId(ivyFile);
                if (moduleId == null) {
                    throw new RuntimeException("Unable to read ivy module id from file " + ivyFile);
                }
                File existingDir = components.get(moduleId);
                if (existingDir != null && !existingDir.equals(moduleDir)) {
                    throw new RuntimeException("Found two components with module id '" + moduleId + "'\n\n " + existingDir + "\n and\n" + moduleDir);
                }
                components.put(moduleId, moduleDir);
            }
            return components;
        }
    }
}
